﻿#region Imports
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Linq;
using CommonUtilities.Extensions;
using CommonUtilities.Messages;
using CommonUtilities.Properties;
#endregion

namespace CommonUtilities.Util
{
    /// <summary>
    /// Allows single-instance and instance-limiting during application start-up
    /// </summary>
    /// <seealso cref="http://blogs.microsoft.co.il/blogs/maxim/archive/2010/02/13/single-instance-application-manager.aspx" />
    /// <seealso cref="http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d7e9120a-f48a-4d3a-aaab-addc8eb6e0da" />
    /// <seealso cref="http://www.codeproject.com/Articles/4430/Single-Instance-Application-in-C" />
    /// <seealso cref="http://stackoverflow.com/questions/184084/how-to-force-c-sharp-net-app-to-run-only-one-instance-in-windows" />
    /// <seealso cref="http://stackoverflow.com/questions/19147/what-is-the-correct-way-to-create-a-single-instance-application" />
    public static class AppInstance
    {
        #region Declarations
        #region Method imports
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
        [DllImport("user32.dll")]
        private static extern bool IsIconic(IntPtr hWnd);
        #endregion Method imports

        #region Constant declarations
        private const int SW_HIDE = 0;
        private const int SW_SHOWNORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;
        private const int SW_SHOWMAXIMIZED = 3;
        private const int SW_SHOWNOACTIVATE = 4;
        private const int SW_RESTORE = 9;
        private const int SW_SHOWDEFAULT = 10;
        #endregion Constant declarations

        #region Delegate declarations
        public static SimpleDelegate PreRun { get; set; }

        public static SimpleDelegate PostRun { get; set; }
        #endregion Delegate declarations
        #endregion

        #region Properties
        /// <summary>
        /// Backing field for &apos;MinAllowed&apos; property
        /// </summary>
        private static int minAllowed = 1;
        /// <summary>
        /// Minimum allowed instances
        /// <para>default: 1</para>
        /// </summary>
        public static int MinAllowed
        {
            get { return minAllowed; }
            set { minAllowed = value; }
        }

        /// <summary>
        /// Backing field for &apos;MaxAllowed&apos; property
        /// </summary>
        private static int maxAllowed = 1;
        /// <summary>
        /// Maximum allowed instances
        /// <para>default: 1</para>
        /// </summary>
        public static int MaxAllowed
        {
            get { return maxAllowed; }
            set { maxAllowed = value; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Validates app instances against specified limit
        /// </summary>
        /// <typeparam name="TForm">Start-up form</typeparam>
        /// <param name="pre">An operation to run before loading of the start-up form</param>
        /// <param name="post">An operation to run after loading of the start-up form</param>
        /// <param name="isDebug">TRUE: Application in debug-mode, either compiler or user -defined</param>
        /// <param name="allowCount">Allowed instance count</param>
        /// <param name="bringRunningToFocus">TRUE: Brings the running instance to focus
        /// <para>(works only if <paramref name="allowCount"/>is set to 1)</para></param>
        public static void ValidateAppInstances<TForm>(SimpleDelegate pre, SimpleDelegate post, bool? isDebug = null, int allowCount = 1, bool bringRunningToFocus = false)
            where TForm : Form, new()
        {
            if (!Utilities.ValidateBetween(allowCount, MinAllowed, MaxAllowed))
                throw new InvalidOperationException(Resources.AppInstance_LimitValidation.FormatTextFromParams(MinAllowed, MaxAllowed));

            bool createdNew = true;
            using (Mutex mutex = new Mutex(true, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase), out createdNew))
            {
                if (createdNew) RunApp<TForm>(pre, post);
                else
                {
                    int processCount = 0;
                    Process current = Process.GetCurrentProcess();

                    foreach (Process process in Process.GetProcessesByName(current.ProcessName))
                    {
                        // get the window handle
                        IntPtr hWnd = process.MainWindowHandle;

                        if (process.Id != current.Id)
                        {
                            if (allowCount == 1)
                            {
                                string msg = bringRunningToFocus ? Resources.AppInstance_ExitAndOpen : Resources.AppInstance_Exit;
                                msg = Resources.AppInstance_Single_Running.FormatTextFromParams(msg);
                                MessageBoxUtil.Show(isDebug.Coalesce(Debugger.IsAttached), msg, current.ProcessName);

                                if (bringRunningToFocus) if (IsIconic(hWnd)) ShowWindowAsync(hWnd, SW_RESTORE); // Bring to front
                                SetForegroundWindow(hWnd); //
                                Application.Exit(); // Exit application

                                return;
                            }
                            else if (allowCount > 1)
                            {
                                processCount++;

                                if (processCount >= allowCount)
                                {
                                    MessageBoxUtil.Show(isDebug.Coalesce(Debugger.IsAttached), Resources.AppInstance_Multi_LimitReached.FormatTextFromParams(allowCount), current.ProcessName);
                                    SetForegroundWindow(process.MainWindowHandle);
                                    Application.Exit();

                                    return;
                                }
                            }
                        }
                    }

                    if (allowCount > 1) RunApp<TForm>(pre, post);
                }
            }
        }

        /// <summary>
        /// Begins running a standard application message loop on the current thread, and makes the specified form visible.
        /// </summary>
        /// <typeparam name="TForm">A <code>System.Windows.Forms.Form</code> that represents the start-up form</typeparam>
        /// <param name="pre">An operation to run before loading of the start-up form</param>
        /// <param name="post">An operation to run after loading of the start-up form</param>
        /// <exception cref="System.InvalidOperationException">You can only call this method before the first window is created by your Windows Forms application.</exception>
        /// <exception cref="System.InvalidOperationException">A main message loop is already running on the current thread.</exception>
        public static void RunApp<TForm>(SimpleDelegate pre, SimpleDelegate post)
            where TForm : Form, new()
        {
            if (!pre.IsNull()) pre();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            try { Application.Run(new TForm { }); }
            catch (TargetInvocationException ex) { throw ex.InnerException; }
            catch (ArgumentOutOfRangeException ex) { throw; }
            catch (Exception ex) { throw; }

            if (!post.IsNull()) post();
        }
        #endregion
    }
}
